﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;
using Eco.ObjectRepresentation;

namespace PlugInRoseFileReadAssembly
{
    public class RoseImport
    {
        private XDocument _XDocument;
        public XDocument XDocument
        { get { return _XDocument; } }

        public void TurnXDocsIntoEcoObjects(IEcoServiceProvider esp, XDocument doc)
        { 
            
        }


        public List<XDocument> CatFiles=new List<XDocument>();
        public void OpenFile(string path,bool readCatFiles)
        {
            StreamReader streamReader = new StreamReader(path, Encoding.GetEncoding("Windows-1252"));
            string text = streamReader.ReadToEnd();
            streamReader.Close();

            _XDocument = XDocument.Parse("<root/>");

            while (text != "")
            {
                string firstobject, rest;
                CutOutFirstParanthesisSection(text, out firstobject, out rest);
                if (firstobject!="")
                    LoadObject(firstobject,null,1,"");
                text = rest;
            }


            // Cat files:
            if (readCatFiles)
            {
                CatFiles.Clear();
                var q = from c in XDocument.Descendants("logical_models") select c;
                foreach (XElement xe in q.Descendants("file_name"))
                {
                    string file = xe.Value.Replace('"',' ').Trim();
                    string catfile=Path.Combine(Path.GetDirectoryName(path), file);
                    if (File.Exists(catfile))
                    {
                        RoseImport ri=new RoseImport();
                        ri.OpenFile(catfile, false);
                        ri.XDocument.Root.SetAttributeValue("filename", catfile);
                        CatFiles.Add(ri.XDocument);
                    }
                }
            }


        
        }

        private void LoadObject(string theobject, object context, int hierarcy,string contextattribname)
        {
            theobject = theobject.Substring(1, theobject.Length - 2); // cut first and last paranthesis
            int lenofrow=theobject.IndexOf('\r');
            if (lenofrow==-1)
                lenofrow=theobject.Length;
            string firstrow = theobject.Substring(0, lenofrow);
            string restofrows = "";
            if (theobject.Length > firstrow.Length + 2)
                restofrows = theobject.Substring(firstrow.Length + 2);
            string name="";
            string[] firstrowparts=firstrow.Split(' ');
            string objecttype = "";
            if (firstrowparts.Length>1)
                objecttype=firstrowparts[1];
            if (firstrow.Contains('"'))
            {
                name = firstrow.Substring(firstrow.IndexOf('"') + 1);
                name = name.Substring(0, name.LastIndexOf('"'));
            }
            string primarykey = "";
            if (firstrow.Contains('@'))
            { 
                primarykey=firstrow.Substring(firstrow.IndexOf('@'));
                CleanRNTFromBack(ref primarykey);
            }

            if (theobject.StartsWith("object"))
            {
                System.Diagnostics.Trace.WriteLine(GetHierStr(hierarcy) + string.Format("object {0} name=\"{1}\" pk={2}", objecttype, name, primarykey));
                object created = CreateObject(context, objecttype, name, primarykey, contextattribname);
                ReadAttributes(restofrows, created, hierarcy+1);
            }
            else if (theobject.StartsWith("list"))
            {
                System.Diagnostics.Trace.WriteLine(GetHierStr(hierarcy) + string.Format("list {0} name=\"{1}\"", objecttype, name));
                while (restofrows != "")
                {
                    string firstlistobject, rest;
                    CutOutFirstParanthesisSection(restofrows, out firstlistobject, out rest);
                    if (firstlistobject.StartsWith("(object"))
                        LoadObject(firstlistobject,context,hierarcy+1,contextattribname);
                    else
                    {
                        System.Diagnostics.Trace.WriteLine(GetHierStr(hierarcy) + string.Format("list name={0} ", firstlistobject));
                    }
                    restofrows = rest;
                }
            }
            else
            {
                throw new Exception("unhandled " + theobject);
            }
        }

        private void CleanRNTFromBack(ref string s)
        {
            while (s.Length > 0 && (s[s.Length - 1] == '\t' || s[s.Length - 1] == '\r' || s[s.Length-1] == '\n'))
                s = s.Substring(0, s.Length-1);
        }

        private void CleanRNTFromFront(ref string s)
        {
            s=s.Trim();
            while (s.Length > 0 && (s[0] == '\t' || s[0] == '\r' || s[0] == '\n'))
                s = s.Substring(1);
        }

        private string GetHierStr(int hierarcy)
        {
            string res = "";
            for (int i = 0; i < hierarcy; i++)
            {
                res += "\t";
            }
            return res;
        }


        private void ReadAttributes(string attributes, object owner, int hierarcy)
        {
            while (attributes != "")
            {
                CleanRNTFromFront(ref attributes);

                int linelength = attributes.IndexOf('\r');
                if (linelength == -1)
                    linelength = attributes.Length;
                string firstrow = attributes.Substring(0, linelength);
                string[] parts = firstrow.Split('\t');
                string attribname=parts[0].Trim();
                string attribvalue = "";
                if (parts.Length>1)
                    attribvalue = parts[1].Trim();
                if ((attribvalue.Contains("(object") || attribvalue.Contains("(list")) &&
                    !(attribvalue.Contains("\\(object") || attribvalue.Contains("\\(list")))  // cannot just look at '(' due to struct values like point (x,y)
                {
                    string firstobject,rest;
                    CutOutFirstParanthesisSection(attributes, out firstobject, out rest);
                    attributes=rest;
                    //System.Diagnostics.Trace.WriteLine(GetHierStr(hierarcy) + string.Format("complex attribute {0} ", attribname));
                    LoadObject(firstobject,owner,hierarcy+1,attribname);
                }
                else
                {
                    
                        if (firstrow.Length + 2 >= attributes.Length)
                            attributes = "";
                        else
                            attributes = attributes.Substring(firstrow.Length + 2);
                        // Values with multiple rows start each new row with pipe
                        while (attributes.Length > 0 && attributes[0] == '|')
                        {
                            int linelength2 = attributes.IndexOf('\r');
                            if (linelength2 == -1)
                                linelength2 = attributes.Length;
                            attribvalue += attributes.Substring(1, linelength2-1);
                            if (attributes.Length > linelength2 + 2)
                                attributes = attributes.Substring(linelength2 + 2);
                            else
                                attributes = "";
                        }
                        SetAttributeValue(owner, attribname, attribvalue);
                        //System.Diagnostics.Trace.WriteLine(GetHierStr(hierarcy)+string.Format("simple attribute {0} = {1}", attribname, attribvalue));
                    }
                
                
            }
        }



        private void CutOutFirstParanthesisSection(string text, out string firstobject, out string rest)
        {
            int start = 0, endpos=0;
            int paranthesisstack=0;
            bool lastwasescape = false;
            for (int i = 0; i < text.Length; i++)
            {
                if (text[i] == '(' && !lastwasescape)
                {
                    start = i;
                    break;
                }

                lastwasescape = text[i]=='\\';
            }

            for (int i = start+1; i < text.Length; i++)
            {
                if (text[i] == '(' && !lastwasescape)
                {
                    paranthesisstack++;
                }
                if (text[i] == ')' && !lastwasescape)
                {
                    if (paranthesisstack == 0)
                    {
                        endpos = i;
                        break;
                    }
                    paranthesisstack--;
                }

                lastwasescape = text[i] == '\\';
            }

            if (endpos - start > 0)
                firstobject = text.Substring(start, endpos - start+1);
            else
                firstobject = "";
            if (endpos + 1 < text.Length)
                rest = text.Substring(endpos + 1);
            else
                rest = "";

            CleanRNTFromFront(ref rest);

        }

        private object CreateObject(object context, string objecttype, string name, string primarykey, string contextattribname)
        {
            XElement xe=new XElement(objecttype);
            XElement xa = new XElement("name");
            xa.Add(new XText(name));
            xe.Add(xa);

            xa = new XElement("key");
            xa.Add(new XText(primarykey));
            xe.Add(xa);

            if (context == null)
                _XDocument.Root.Add(xe);
            else
            {
                IEnumerable<XElement> list=(context as XElement).Elements(contextattribname);               
                if (list.Count<XElement>() == 0)
                {
                    xa = new XElement(contextattribname);
                    (context as XElement).Add(xa);
                }
                else
                {
                    xa = list.Last<XElement>();
                }
                xa.Add(xe);

            }

            return xe;
        }
        private void SetAttributeValue(object owner, string attribname, string attribvalue)
        {
            if (owner is XElement)
            {
                XElement xe = new XElement(attribname);
                xe.Add(new XText(attribvalue));
                (owner as XElement).Add(xe);
            }
        }

    }
}
